<?php
/**
 *  Generic Abstract Model: per-row table access
 *
 *  Similar to Active Record pattern, in which the record is implemented in Item,
 *      and the data access in Action.
 *  Implement the common methods of selecting items and executing actions
 *
 */
namespace Yz\modules\Generic;

abstract class Model_Abstract
    extends \Yz_Model_Abstract
    implements \Yz_Model_Interface
{

    const DEFAULT_ITEM = "Default";
    const SOURCE_CONFIG = null;
    const SOURCE_DEFAULT = '~';
    const SOURCE_DEFAULT_ALIAS = 't0';

    /**
     *  @var array|null
     */
    protected static $sourceConfig = self::SOURCE_CONFIG;

    /**
     *  @var Yz_Hash
     */
    protected $sourceDescriptor;

    /**
     *  a name of item class (Yz_Item_Interface)
     *  @var string
     */
    protected $itemName = self::DEFAULT_ITEM;

    /** the list of loaded item template objects
     *  @var array
     */
    protected $items = array();



    /************************************
     *          Public methods          *
     ************************************/


    /**
     *  get item class
     *  @return string
     */
    public function fetchItemConfig(\Yz_Item_Interface $item)
    {
        debug();
        return $this->module
            ->getConfig()
            ->fetchSectionWithInherit('Item.'.$item->shortClassName())
            ;
    }


    /**
     *  create item template
     *  @return Yz_Item_Interface
     */
    public function selectItem($name=null)
    {
        debug($name);
        if (null === $name) {
            $name = $this->itemName;
        } else {
            $this->itemName = $name;
        }

        if (array_key_exists($name, $this->items)) {
            $itemClass = $this->items[$name];
        } else {
            $itemClass = $this->identifyItemClass($name);
            $this->items[$name] = $itemClass;
        }
        return new $itemClass($this);
    }



    /************************************
     *       Protected methods          *
     ************************************/



    /**
     *  identify item class
     *  @return string
     */
    protected function identifyItemClass($name)
    {
        debug();
        $class = $this->module->resolveLibrary('Item', $name);
        assert('is_subclass_of($class,"Yz_Item_Interface")');
        return $class;
    }


    /**
     *  create action
     *  @return Yz_Action_Interface
     */
    protected function selectAction( $actionName )
    {
        debug();
        $class = $this->module->resolveLibrary('Action', $actionName);
        assert('is_subclass_of($class,"Yz_Action_Interface")');

        // TODO take class name from property of $this
        $action = new $class($this->describeSource());

        return $action;
    }


    /**
     *  get a hash with item's source names (SQL table aliases) as keys and SQL table names as values
     *
     *  The default implementation returns one source entry
     *  with the table name built from the current module's namespace
     *  and with the alias 't0'
     *  @return Yz_Hash
     *  @todo use config 'sources'
     */
    protected function getSourceFromConfig()
    {
        debug();
        $out = array();
        $defaultSource = $this->getDefaultSource();
        $s = $this->getConfig()->fetchSection("source");
        if (null === $s) {
            $out[static::SOURCE_DEFAULT_ALIAS] = $defaultSource;
#            throw new \Yz_Exception("Failed to identify sources");
        } else {
            foreach ($s as $alias => $data) {
                if (is_string($data)) {
                    $src = str_replace(static::SOURCE_DEFAULT, $defaultSource, $data);
                } else {
                    $src = $data;
                    if ($src->offsetExists('name')) {
                        $src['name'] = str_replace(static::SOURCE_DEFAULT, $defaultSource, $src['name']);
                    } else {
                        // ERROR, name must be present
                    }
                }
                $out[$alias] = $src;
            }
        }
        return new \Yz_Hash($out);
    }


    /**
     *  get a hash with item's source names (SQL table aliases) as keys and SQL table names as values
     *
     *  The default implementation returns one source entry
     *  with the table name built from the current module's namespace
     *  and with the alias 't0'
     *  @return Yz_Hash
     */
    protected function getSourceFromStatic()
    {
        debug();
        $s = static::$sourceConfig;
        $out = array();
        $defaultSource = $this->getDefaultSource();
        if (is_string($s)) {
            $out[static::SOURCE_DEFAULT_ALIAS] = str_replace(static::SOURCE_DEFAULT, $defaultSource, $s);
        } else {
            foreach ($s as $alias => $src) {
                if (is_string($src)) {
                    $src = str_replace(static::SOURCE_DEFAULT, $defaultSource, $src);
                } else {
                    if (array_key_exists('name', $src)) {
                        $src['name'] = str_replace(static::SOURCE_DEFAULT, $defaultSource, $src['name']);
                    } else {
                        // ERROR, name must be present
                    }
                    $src = new \Yz_Hash($src);
                }
                $out[$alias] = $src;
            }
        }
        return new \Yz_Hash($out);
    }

    /**
     *
     *  @return string - lowercase module-name
     */
    protected function getDefaultSource()
    {
        debug();
        return strtolower($this->module->getName());
    }

    protected function describeSource()
    {
        $s =& $this->sourceDescriptor;
        if (null === $s) {
            if (self::SOURCE_CONFIG === static::$sourceConfig) {
                $s = $this->getSourceFromConfig();
            } else {
                $s = $this->getSourceFromStatic();
            }
        }
        return $s;
    }

}
